(function() {

	if (!window.console) window.console = {
		log : function() {
		},
		error : function() {
		}
	};

	/*----------------- begin utils(private) -------------------*/

	var utils = {};
	utils.hasCORS = window.XMLHttpRequest && (function() {
		try {
			var req = new XMLHttpRequest();
		} catch (e) {
			return false;
		}
		return req.withCredentials != undefined;
	})();
	utils.inherit = function(class1, class2) {
		function f() {
		}
		f.prototype = class2.prototype;
		class1.prototype = new f;
	};
	utils.getXHR = function() {
		if (window.XDomainRequest) return new XDomainRequest();
		if (window.XMLHttpRequest && utils.hasCORS) return new XMLHttpRequest();
		return null;
	};
	utils.getDomain = function() {
		var host = window.location.host;
		var portIndex = host.indexOf(':');
		if (portIndex > 0) host = host.substring(0, portIndex);
		var hostParts = host.split('\.');
		if (hostParts.length == 3) return hostParts[1] + '.' + hostParts[2];
		return host;
	};
	var jsonAnalyer = window.JSON;
	if (!jsonAnalyer) {
		jsonAnalyer = {};
		jsonAnalyer.parse = function(str) {
			return (new Function('return ' + str))();
		};
		jsonAnalyer.stringify = function(obj) {
			var t = typeof (obj);
			if (t != "object" || obj === null) {
				if (t == "string") obj = '"' + obj + '"';
				return String(obj);
			} else {
				var n, v, json = [], arr = (obj && obj.constructor == Array);
				for (n in obj) {
					v = obj[n];
					t = typeof (v);
					if (t == "string") v = '"' + v + '"';
					else if (t == "object" && v !== null) v = JSON.stringify(v);
					json.push((arr ? "" : '"' + n + '":') + String(v));
				}
				return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
			}
		}
	}
	utils.JSON = jsonAnalyer;
	utils.empty = function() {
	};
	utils.isArray = function(obj) {
		if (typeof (obj) == 'object' && typeof (obj.sort) == 'function' && typeof (obj.length) == 'number') return true;
		return false;
	};

	/*------------------ end utils(private) --------------------*/

	Ymer = function(url, transport) {
		var self = this;
		self.url = url;
		self.isConnected = false;
		self.events = {};
		self.sid = null;

		if (transport == 'websocket') self.transport = new WebSocketTransport(self);
		else if (transport == 'httpstreaming') self.transport = new HttpStreamingTransport(self);
		else if (transport = 'longpolling') self.transport = new LongPollingTransport(self);
		else throw ('Unknow transport type:' + transport);
	};

	Ymer.prototype.on = function(topic, fn) {
		var self = this;
		if (typeof (fn) != 'function') return;
		self.events[topic] = fn;
	};

	Ymer.prototype.un = function(topic) {
		var self = this;
		delete self.events[topic];
	};

	Ymer.prototype.emit = function(topic, message) {
		var self = this;
		if (!self.isConnected) throw ('ymer disconnected.');
		self.transport.send({
			type : 5,
			data : {
				topic : topic,
				message : message
			}
		});
	};

	Ymer.prototype.disconnect = function() {
		var self = this;
		self.transport.disconnect();
	};

	Ymer.prototype.onData = function(data) {
		var self = this;

		switch (data.type) {
		case 1: // ConnectCommand
			__onConnect(data.data);
			break;
		case 2: // DisconnectCommand
			__onDisconnect(data.data);
			break;
		case 3: // PingCommand
			__onPing();
			break;
		case 5: // PubCommand
			__onMessage(data.data);
			break;
		}

		function __onConnect(sid) {
			self.isConnected = true;
			self.sid = sid;
			var connectFn = self.events['connect'];
			if (connectFn) connectFn.apply(self, [ sid ]);
		}

		function __onDisconnect(reason) {
			if (!self.isConnected) return;
			self.transport.destory();
			self.sid = null;
			self.isConnected = false;
			var disconnectFn = self.events['disconnect'];
			if (disconnectFn) disconnectFn.apply(self, [ reason ]);
		}

		function __onMessage(message) {
			if (!self.isConnected) return;
			var topic = message.topic, data = message.message;
			if (!topic || !data) return;
			var topicFn = self.events[topic];
			if (topicFn) topicFn.apply(self, [ data ]);
		}

		function __onPing() {
			if (!self.isConnected) return;
			self.transport.send({
				type : 4
			});
		}
	};

	/*----------------- begin transport(private) -------------------*/

	// ---------------------: WebSocket Transport
	var WebSocketTransport = function(ymer) {
		var self = this;
		self.ymer = ymer;
		var wsUrl = 'ws://' + ymer.url + '/ymer/websocket';
		self.socket = window.MozWebSocket ? new MozWebSocket(wsUrl) : new WebSocket(wsUrl);
		self.socket.onmessage = function(message) {
			var data = message.data;
			if (!data) return;
			if (typeof (data) == 'string') data = utils.JSON.parse(data);
			ymer.onData(data);
		};
		self.socket.onclose = function() {
			ymer.onData({
				type : 2,
				data : null
			});
		};
	};
	WebSocketTransport.prototype.send = function(message) {
		var self = this;
		self.socket.send(utils.JSON.stringify(message));
	};
	WebSocketTransport.prototype.disconnect = function() {
		var self = this;
		self.socket.close();
		self.socket = null;
	};
	WebSocketTransport.prototype.destory = utils.empty;

	// ---------------------: HttpStreaming Transport
	var HttpStreamingTransport = function(ymer) {
		var self = this;
		var iframeSrc = 'http://' + ymer.url + '/ymer/httpstreaming';
		var innerIframe;
		self.ymer = ymer;
		self.request = new Request(iframeSrc);
		document.domain = utils.getDomain();
		window.__ymer_c_b = function(data, doc) {
			ymer.onData(data);
			try {
				var script = doc.getElementsByTagName('script')[0];
				script.parentNode.removeChild(script);
			} catch (e) {
				log.error(e);
			}
		}

		document.onunload = function() {
			self.destory();
		};

		if (!-[ 1, ]) { // for IE
			var htmlfileCon = new ActiveXObject('htmlfile');
			htmlfileCon.open();
			htmlfileCon.write('<html>');
			htmlfileCon.write('<script type="text/javascript">document.domain=\'' + document.domain + '\';<\/script>');
			htmlfileCon.write('</html>');
			htmlfileCon.close();

			innerIframe = htmlfileCon.createElement('iframe');
			innerIframe.id = 'ymer_hs_iframe';
			innerIframe.src = iframeSrc;
			htmlfileCon.parentWindow.__ymer_c_b = window.__ymer_c_b;
			setInterval(function() { // 防止htmlfile自动断开
			}, 5000);
			htmlfileCon.body.appendChild(innerIframe);
		} else {
			innerIframe = document.createElement('iframe');
			innerIframe.id = 'ymer_hs_iframe';
			innerIframe.src = iframeSrc;
			innerIframe.style.display = 'none';
			document.body.appendChild(innerIframe);
		}

		self.innerIframe = innerIframe;
	};
	HttpStreamingTransport.prototype.send = function(content) {
		var self = this;
		var data = {
			sid : self.ymer.sid,
			content : content
		};
		self.request.post(data);
	};
	HttpStreamingTransport.prototype.disconnect = function() {
		var self = this;
		self.send({
			type : 2
		});
	};
	HttpStreamingTransport.prototype.destory = function() {
		var self = this;
		self.innerIframe.src = 'about:blank';
		self.innerIframe.parentNode.removeChild(self.innerIframe);
		self.innerIframe = null;
	};

	// ---------------------: LongPolling Transport
	var LongPollingTransport = function(ymer) {
		var self = this;
		var pollingUrl = 'http://' + ymer.url + '/ymer/longpolling';
		self.ymer = ymer;
		self.request = new Request(pollingUrl);
		self.continuePolling = true;
		var cbs = {
			onData : function(data) {
				if (utils.isArray(data)) {
					for ( var i = 0; i < data.length; i++)
						self.ymer.onData(data[i]);
				} else {
					self.ymer.onData(data);
				}
				if (self.continuePolling) polling();
			},
			onError : function() {
				self.ymer.disconnect();
			}
		};
		function polling() {
			self.request.get(cbs, ymer.sid);
		}
		polling();
	};

	LongPollingTransport.prototype.send = function(content) {
		var self = this;
		var data = {
			sid : self.ymer.sid,
			content : content
		};
		self.request.post(data);
	};
	LongPollingTransport.prototype.disconnect = function() {
		var self = this;
		self.send({
			type : 2
		});
	};
	LongPollingTransport.prototype.destory = function() {
		var self = this;
		self.continuePolling = false;
	};

	/*------------------ end transport(private) --------------------*/

	/*----------------- begin Request(private) -------------------*/
	var Request = function(url) {
		this.url = url;
	};
	Request.prototype.get = function(cbs, sid) {
		var self = this;
		function stateChange() {
			if (this.readyState == 4) {
				if (this.status == 200) {
					cbs.onData(utils.JSON.parse(this.responseText));
				} else {
					cbs.onError();
				}
			}
		}
		function onload() {
			cbs.onData(utils.JSON.parse(this.responseText));
		}
		function onerror() {
			cbs.onError();
		}

		var xhr = utils.getXHR();
		if (xhr != null) {
			if (window.XDomainRequest && (xhr instanceof XDomainRequest)) {
				xhr.onload = onload;
				xhr.onerror = onerror;
			} else {
				xhr.onreadystatechange = stateChange;
			}
			var url = self.url;
			url = sid ? (url + '?sid=' + sid) : url;
			xhr.open('get', url);
			// 对于get方式的XHR，不能使用send发送参数（发送了也接受不了），只能通过修改URL的方式来传递参数
			xhr.send(null);
			return;
		}
		// use jsonp
		function loadScript(url) {
			var head = document.getElementsByTagName('head')[0];
			var script = document.createElement('script');
			script.type = 'text/javascript';
			script.src = url;
			script.async = true;
			script.onload = script.onreadystatechange = function() {
				if ((!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) {
					script.onload = script.onreadystatechange = null;
					if (head && script.parentNode) head.removeChild(script);
				}
			};
			script.onerror = onerror;
			head.insertBefore(script, head.firstChild);
		}
		window.__ymer_c_b = function(data) {
			cbs.onData(data);
		};
		var jsonpUrl = self.url + '?jsonp=true';
		if (sid) jsonpUrl += ('&sid=' + sid);
		loadScript(jsonpUrl);
	};
	/*------------------ end Request(private) --------------------*/
	Request.prototype.post = function(data) {

		var self = this;

		// post by xhr(xdr)
		var xhr = utils.getXHR();
		if (xhr != null) {

			var param = '';
			param += ('sid=' + data.sid);
			param += '&';
			param += ('content=' + utils.JSON.stringify(data.content));
			xhr.open('post', self.url);
			xhr.send(param);
			return;
		}
		// post by hidden form
		var iframeName = '__target_iframe';
		if (!self.postForm) {
			var form = document.createElement('form');
			var sidField = null;
			var contentField = null;
			// ie6,7下不能给input动态设置name属性，所以必须通过IE的createElement+html方式创建input（这样方式在其他浏览器下不适用）
			try {
				sidField = document.createElement('<input type="text" name="sid">');
				contentField = document.createElement('<input type="text" name="content">');
			} catch (e) {
				sidField = document.createElement('input');
				contentField = document.createElement('input');
				sidField.name = 'sid';
				contentField.name = 'content';
			}
			form.style.display = 'none';
			form.target = iframeName;
			form.method = 'post';
			form.action = self.url;
			document.body.appendChild(form);
			form.appendChild(sidField);
			form.appendChild(contentField);
			self.postForm = form;
			self.contentField = contentField;
			self.sidField = sidField;
		}

		self.sidField.value = data.sid;
		self.contentField.value = utils.JSON.stringify(data.content);

		function createPostIFrame() {
			if (self.postIFrame) self.postForm.removeChild(self.postIFrame);
			var iframe = document.createElement('iframe');
			iframe.name = iframeName;
			self.postForm.appendChild(iframe);
			// 解决ie6,7下仍然会打开新窗口的bug
			if (!-[ 1, ]) iframe.contentWindow.name = iframeName;
			self.postIFrame = iframe;
		}
		createPostIFrame();

		self.postForm.submit();

		if (self.postIFrame.attachEvent) {
			self.postIFrame.onreadystatechange = function() {
				if (self.postIFrame.readyState == 'complete') {
					createPostIFrame();
				}
			};
		} else {
			self.postIFrame.onload = createPostIFrame;
		}
	};
})();
